/************************************************************************/
/* MIME_PARSE
*/
/************************************************************************/

#include <iostream>
#include <vector>
#include <set>
#include <iterator>
#include <string>
#include <fstream>
#include <algorithm>
#include <cstdlib>
using namespace std;

//#define TEST

//key words
const char KEY_BOUNDARY[] = "boundary=\"";
const char KEY_CONTENT_TYPE[] = "Content-Type: multipart";
//const char KEY_MULTIPART[] = "multipart";
const char KEY_CONTENT_TRANSFER[] = "Content-Transfer-Encoding: ";
const char KEY_CONTENT_DISPOSITION[] = "Content-Disposition: ";

//store the boundary code
vector<const char*> m_Boudary;

struct MIME_PART
{
	MIME_PART*		first_child;
	MIME_PART*		next_sibling;
	int				multipart;
	int				header_offset;
	int				content_offset;
	int				content_lines;

	MIME_PART():first_child(NULL), next_sibling(NULL), multipart(0),
		header_offset(0),content_offset(0),content_lines(0)
	{
	}
};

vector<MIME_PART*> pCur_Mime_part;//top always be current top father or pre-subling
MIME_PART* pCurFather = NULL;


//************************************
// Method:    ContentLines
// FullName:  ContentLines
// Access:    public 
// Returns:   void
// Qualifier:
// Parameter: char * pEntity
// Parameter: int & nlines
//************************************
void ContentLines(char* pEntity, int& nlines )
{
	char* pFind = strstr( pEntity, "\n" );
	while(pFind++ != NULL )
	{	
		nlines++;
		pFind = strstr(pFind, "\n" );
	}
}


//************************************
// Method:    ParseEntity
// FullName:  ParseEntity
// Access:    public 
// Returns:   void
// Qualifier:
// Parameter: MIME_PART *  & pCur
// Parameter: char * pEntity => child mime-part
// Parameter: int offset => current pEntity's offset in the total email
//************************************
void ParseEntity(MIME_PART* &pCur , char* pEntity, int offset )
{	
	if( pCur == NULL )
		pCur = new MIME_PART();
	pCur_Mime_part.push_back( pCur );
	string* sBoudaryCode = NULL ;
	int boudarycodelength;
	char *pdest = NULL,
		 *pE1End;
	char *pE1Start;


	//header parse
	pdest = strstr( pEntity, KEY_CONTENT_TYPE );
	if( pdest != NULL ) 
	{
		pCur->multipart = 1; 
		pdest = strstr( pEntity, KEY_BOUNDARY );
		if( pdest != NULL )
		{
			char* pStart = pdest+sizeof(KEY_BOUNDARY);
			pStart = strstr( pStart, "\"" );
			vector<char> v;
			v.push_back( '-' );
			v.push_back( '-' );
			v.insert( v.end(), pdest+sizeof(KEY_BOUNDARY)-1, pStart );
			boudarycodelength = v.size();
			sBoudaryCode = new string( v.begin(), v.end() );
			m_Boudary.push_back( sBoudaryCode->c_str() );
			pdest = pStart;
			pdest++;
		}
	}
	else
	{ 
		//if it's not a multipart 
		//we just go to the body part
		pdest = strstr( pEntity, "\n\n");
		//pCur_Mime_part.pop_back();
	}
	
	//skip 2 \n\r
	//skip the crlf line
	char* temp = "\n\r";
	char* itemp = "-";
	while( *pdest == *temp )
	{
		pdest++;
	}

	//body part
	pCur->header_offset = offset;
	pCur->content_offset = pdest - pEntity + 1 + offset ;
	
	ContentLines( pdest, pCur->content_lines );
	
	//parse body contents
	//begin split child mime_part
	if( pCur->multipart )
	{
		//just search for the newly add boudary code
		pE1Start = strstr( pdest, m_Boudary.back() );
		int ioffset = 0;
		pCurFather = pCur_Mime_part.back();
		while( pE1Start != NULL )
		{
			pE1Start += boudarycodelength;
			if( *pE1Start == *temp )
			{
				pE1Start++;//skip "\n\r"s
			}
			else if( *pE1Start == *itemp )
			{	
				m_Boudary.pop_back();//when it comes to end we just pop back the code
				pCur_Mime_part.pop_back();
				return ;
			}
			ioffset = pE1Start - pEntity + 1 + offset;
			
			pE1End = strstr( pE1Start, m_Boudary.back() );
			char *pTemp = pE1End;
			pTemp--;
			vector<char> entity( pE1Start, pTemp );
			if( pCurFather->first_child == NULL )
				ParseEntity( pCurFather->first_child, &entity[0], ioffset );
			else
			{
				MIME_PART* ptemp = pCur_Mime_part.back();
				pCur_Mime_part.pop_back();
				ParseEntity( ptemp->next_sibling, &entity[0], ioffset );
			}
			pTemp++;
			pE1Start = pTemp;
		}
	}
	return ;
}

#ifdef TEST
void Print(char Iter)
{
	cout<<Iter;
}
#endif

//************************************
// Method:    CleanUp
// FullName:  CleanUp
// Access:    public 
// Returns:   void
// Qualifier:
// Parameter: MIME_PART * part
//************************************
void CleanUp(MIME_PART* part)
{
	if( part )
	{
		CleanUp( part->first_child );
		CleanUp( part->next_sibling );
		delete part;
	}
}




//************************************
// Method:    Format_Output
// FullName:  Format_Output
// Access:    public 
// Returns:   void
// Qualifier:
// Parameter: ofstream & file
// Parameter: MIME_PART * part
// Parameter: int ndepth
//************************************
void Format_Output(ofstream& file, MIME_PART* part, int ndepth )
{
	if( part == NULL )
		return; 
	vector<char> iFormat;
	string iFormatString = "";
	if( ndepth )
	{
		iFormat.reserve( ndepth );
		for( int i =0; i<ndepth;++i ) { iFormat.push_back( '\t' ); }
		iFormatString += string(iFormat.begin(), iFormat.end() );
	}
	string iMultipart = "Multipart: ";
	string iHeader_offset = "Header_offset: ";
	string iContent_offset = "Content_offset: ";
	string iContent_lines = "Content_lines: ";
	
	string iOutput = "";
	iOutput = iFormatString + iMultipart;
	file<<iOutput<<part->multipart<<endl;
	iOutput.clear();
	iOutput = iFormatString+iHeader_offset;
	file<<iOutput<<part->header_offset<<endl;
	iOutput.clear();
	iOutput = iFormatString+iContent_offset;
	file<<iOutput<<part->content_offset<<endl;
	iOutput.clear();
	iOutput = iFormatString+iContent_lines;
	file<<iOutput<<part->content_lines<<endl;
	file<<endl;
	int ntemp = ndepth;
	if( part->first_child != NULL )
	{
		ntemp++;
		Format_Output( file, part->first_child, ntemp );
		//part = part->first_child;
	}
	Format_Output(file, part->next_sibling, ndepth );
	return ;
}

int main()
{
	string filename = "txt+html+att(eml).eml";//"txt+html.eml";
	ifstream inputfile;
	ofstream  outputfile("txt+html+att(eml).txt" );
	if( !outputfile.is_open() )
	{
		cerr<<"can't open"<<endl;
		outputfile.clear();
	}
	inputfile.open( filename.c_str() );
	if( !inputfile.is_open() )
	{
		cerr<<"couldn't open"<<endl;
		inputfile.clear();
	}
	MIME_PART* pTreeNode = new MIME_PART();
	vector<char> fileData((istreambuf_iterator<char>(inputfile)), istreambuf_iterator<char>());

	//for_each( fileData.begin(), fileData.end(), Print);
	
	ParseEntity( pTreeNode, &fileData[0], 0  );

	Format_Output(outputfile, pTreeNode, 0 );
	
	outputfile.close();
	inputfile.close();
	vector<const char*>::iterator iter = m_Boudary.begin();
	while( iter != m_Boudary.end() )
	{
		//iter = m_Boudary.erase( iter );
		delete *(iter++);
	}
	
	CleanUp( pTreeNode );

	return 0;
}
